/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.muni.fi.rum.sender.senders;

import cz.muni.fi.rum.reflector.telnet.TelnetServer;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author pmikulasek
 */
public class SenderFactoryImpl implements SenderFactory {

    private Socket socket;
    private static SenderFactory instance;
    private volatile MessageSender presentSender;
    private static final Logger log = LoggerFactory.getLogger(SenderFactoryImpl.class);

    private SenderFactoryImpl() {
    }

    public static SenderFactory getInstance() {
        if (instance == null) {
            instance = new SenderFactoryImpl();
        }
        return instance;
    }

    @Override
    public synchronized MessageSender createAndConnectNewMessageSender(TelnetServer server) throws IOException {
        if (server == null) {
            //vytvoreni mock sluzby
            getBufferSender().sendAllRequests();
            closeSocket();
            socket = null;
            presentSender = new MessageSenderImpl(new OutputStreamWriter(System.out), new InputStreamReader(System.in));
            return presentSender;
        } else {

            //TODO ==SENDER== Moznosti pro dodelani, lepsi funkcnosti.
            //Momentalne pri pozadavku na pripojeni se jednoduse
            //odeslou vsechny zpravy, uzavre se puvodni pripojeni
            //a pak se teprve vytvori novy message sender.
            //=====
            //dalo by se urcite i osetrit vyjimecny stav, kdy jsou v tomto momentu
            //ve fronte na odeslani jeste nejake zpravy i druhym alternativnim zpusobem,
            //a to vracenim vyjimky o teto udalosti
            getBufferSender().sendAllRequests();

            closeSocket();
            InetSocketAddress addr = new InetSocketAddress(server.getAddress(), server.getPort());
            socket = new Socket();
            try {
                socket.connect(addr); //pokusit se připojit
            } catch (SocketTimeoutException ex) {
                presentSender = null;
                closeSocket();
                log.error("Can't connect to server", ex);
                throw new IOException("Nelze se připojit k serveru: " + addr, ex);
            } catch (UnknownHostException ex) {
                presentSender = null;
                closeSocket();
                log.error("Unknown host", ex);
                throw new IOException("Unknown Host: " + addr, ex);
                /* Jiná IO výjimka. Obvykle NoRouteToHostException nebo ConnectException. */
            } catch (IOException ex) {
                presentSender = null;
                log.error("Unexpected IO problem.", ex);
                closeSocket();
                throw ex;
            }
            try {
                presentSender = new MessageSenderImpl(new OutputStreamWriter(socket.getOutputStream(), "US-ASCII"), new InputStreamReader(socket.getInputStream()));
            } catch (IOException ex) {
                //nepodarilo se ziskat streamy ze socketu
                presentSender = null;
                log.error("Can't get streams from socket.", ex);
                closeSocket();
                throw ex;
            }
            return presentSender;
        }

    }

    @Override
    public synchronized MessageSender getPresentMessageSender() {
        if (presentSender == null) {
            throw new IllegalStateException("Connection isn't avalaible.");
        }
        return presentSender;
    }

    @Override
    public MainSender getBufferSender() {
        return BufferSender.getInstance();
    }

    @Override
    public MainSender getForceSender() {
        return ForceSender.getInstance();
    }

    private void closeSocket() {
        if (socket != null) {
            if (!socket.isClosed()) {
                presentSender = null;
                try {
                    socket.close();
                } catch (IOException ex) {
                    log.error("Unexpected IO problem.", ex);
                }
            }
        }
    }
}
